from PySide6 import QtCore, QtWidgets
import copy
import enum


class Reactive:
    collecting: list = None

    def __init__(self, value):
        self.value = value
        self.onValChanged0 = []
        self.onValChanged1 = []
        self.onValChanged2 = []

    def get(self):
        if Reactive.collecting is not None:
            Reactive.collecting.append(self)
        return self.value

    def set(self, value):
        if self.value != value:
            old = self.value
            self.value = value
            for callback in self.onValChanged0:
                callback()
            for callback in self.onValChanged1:
                callback(value)
            for callback in self.onValChanged2:
                callback(old, value)


class Computed(Reactive):
    def __init__(self, callback):
        assert Reactive.collecting is None
        Reactive.collecting = []
        super().__init__(callback())
        Reactive.collecting.sort(key=lambda reactive: reactive.__hash__())
        for reactive in Reactive.collecting:
            reactive.onValChanged0.append(lambda: self.set(callback()))
        Reactive.collecting = None


class ReactiveContainer:
    def __init__(self):
        self.reactives = []

    def createComputed(self, callback):
        computed = Computed(callback)
        self.reactives.append(computed)
        return computed


class BindMode(enum.Enum):
    Set = 1
    Get = 2
    Spread = 4
    Feedback = 8
    Set_Spread = Set | Spread
    Set_Feedback = Set | Feedback

    def __or__(self, other):
        return BindMode(self.value | other.value)

    def __and__(self, other):
        return self.value & other.value


class PropertyBinding:
    def __init__(self, reactive: Reactive, bindMode: BindMode):
        self.reactive = reactive
        self.bindMode = bindMode


class IObject:
    def __init__(self, object: QtCore.QObject, **kwargs):
        assert object is not None
        self.object = object
        for key, value in kwargs.items():
            match value:
                case PropertyBinding():
                    self._bind_property(key, value)
                case _:
                    self._set_property(key, value)

    def _set_property(self, key: str, value):
        assert self.object.setProperty(key, value)

    def _bind_property(self, key: str, value: PropertyBinding):
        if value.bindMode & BindMode.Set:
            self._set_property(key, value.reactive.get())
        if value.bindMode & BindMode.Get:
            value.reactive.set(self.object.property(key))
        if value.bindMode & BindMode.Spread:
            value.reactive.onValChanged1.append(
                lambda value: self._set_property(key, value)
            )
        if value.bindMode & BindMode.Feedback:
            mo = self.object.metaObject()
            propertyIndex = mo.indexOfProperty(key)
            assert propertyIndex != -1
            property = mo.property(propertyIndex)
            assert property.hasNotifySignal()
            QtCore.QObject.connect(
                self.object,
                QtCore.SIGNAL(property.notifySignal().methodSignature().toStdString()),
                lambda: value.reactive.set(self.object.property(key)),
            )


class IWidget(IObject):
    def __init__(
        self, widget: QtWidgets.QWidget, layout: QtWidgets.QLayout = None, **kwargs
    ):
        super().__init__(widget, **kwargs)
        self.widget = widget
        match layout:
            case ILayout():
                self.widget.setLayout(layout.layout)
            case QtWidgets.QLayout():
                self.widget.setLayout(layout)
            case None:
                pass
            case _:
                assert False


class ILayout(IObject):
    def __init__(self, layout: QtWidgets.QLayout, **kwargs):
        super().__init__(layout, **kwargs)
        self.layout = layout


class IModel(IObject):
    def __init__(self, model: QtCore.QAbstractItemModel, **kwargs):
        super().__init__(model, **kwargs)
        self.model = model


class Stretch:
    def __init__(self, stretch: int = 0):
        self.stretch = stretch


class NextStretch:
    def __init__(self, stretch: int = 0):
        self.stretch = stretch


class BoxLayout(ILayout):
    def __init__(self, layout: QtWidgets.QBoxLayout, children: list = None, **kwargs):
        super().__init__(layout, **kwargs)
        self.layout = layout
        next_stretch = 0
        if children is not None:
            for child in children:
                match child:
                    case IWidget():
                        self.layout.addWidget(child.widget, stretch=next_stretch)
                        next_stretch = 0
                    case ILayout():
                        self.layout.addLayout(child.layout, stretch=next_stretch)
                        next_stretch = 0
                    case Stretch():
                        self.layout.addStretch(child.stretch)
                    case NextStretch():
                        next_stretch = child.stretch
                    case QtWidgets.QWidget():
                        self.layout.addWidget(child, stretch=next_stretch)
                        next_stretch = 0
                    case QtWidgets.QLayout():
                        self.layout.addLayout(child, stretch=next_stretch)
                        next_stretch = 0
                    case _:
                        assert False


class VBoxLayout(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(QtWidgets.QVBoxLayout(), **kwargs)


class HBoxLayout(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(QtWidgets.QHBoxLayout(), **kwargs)


class Widget(IWidget):
    def __init__(self, **kwargs):
        self.widget = QtWidgets.QWidget()
        super().__init__(self.widget, **kwargs)


class AbstractItemView(IWidget):
    def __init__(
        self,
        widget: QtWidgets.QAbstractItemView,
        model: QtCore.QAbstractItemModel = None,
        **kwargs,
    ):
        super().__init__(widget, **kwargs)
        self.widget = widget
        if model is not None:
            match model:
                case IModel():
                    self.widget.setModel(model.model)
                case QtCore.QAbstractItemModel():
                    self.widget.setModel(model)


class StringListModel(IModel):
    def __init__(self, stringList, **kwargs):
        self.model = QtCore.QStringListModel()
        super().__init__(self.model, **kwargs)
        match stringList:
            case list():
                self.model.setStringList(stringList)
            case Reactive():
                self.model.setStringList(stringList.get())
                stringList.onValChanged0.append(
                    lambda: self.model.setStringList(stringList.get())
                )


class ListView(AbstractItemView):
    def __init__(self, **kwargs):
        self.widget = QtWidgets.QListView()
        super().__init__(self.widget, **kwargs)


class MainWindow(IWidget):
    def __init__(self, centralWidget: QtWidgets.QWidget = None, **kwargs):
        self.widget = QtWidgets.QMainWindow()
        super().__init__(self.widget, **kwargs)
        match centralWidget:
            case IWidget():
                self.widget.setCentralWidget(centralWidget.widget)
            case QtWidgets.QWidget():
                self.widget.setCentralWidget(centralWidget)
            case _:
                assert False

    def show(self):
        self.widget.show()


class GroupBox(IWidget):
    def __init__(self, title: str = None, **kwargs):
        self.widget = QtWidgets.QGroupBox()
        super().__init__(self.widget, **kwargs)
        if title is not None:
            self.widget.setTitle(title)


class AbstractButton(IWidget):
    def __init__(self, widget: QtWidgets.QAbstractButton, onClicked=None, **kwargs):
        self.widget = widget
        super().__init__(self.widget, **kwargs)
        if onClicked is not None:
            self.widget.clicked.connect(onClicked)


class PushButton(AbstractButton):
    def __init__(self, **kwargs):
        self.widget = QtWidgets.QPushButton()
        super().__init__(self.widget, **kwargs)


class CheckBox(AbstractButton):
    def __init__(self, **kwargs):
        self.widget = QtWidgets.QCheckBox()
        super().__init__(self.widget, **kwargs)


class Label(IWidget):
    def __init__(self, **kwargs):
        self.widget = QtWidgets.QLabel()
        super().__init__(self.widget, **kwargs)


class ComboBox(IWidget):
    def __init__(
        self,
        model: QtCore.QAbstractItemModel = None,
        **kwargs,
    ):
        self.widget = QtWidgets.QComboBox()
        super().__init__(self.widget, **kwargs)
        if model is not None:
            match model:
                case IModel():
                    self.widget.setModel(model.model)
                case QtCore.QAbstractItemModel():
                    self.widget.setModel(model)
